package com.breeze.components.message.secure;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.breeze.components.message.secure.record.SecureInvokeRecord;
import com.breeze.components.message.secure.record.SecureInvokeRecordService;
import jakarta.validation.constraints.NotNull;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * 安全执行业务
 *
 * @author breeze
 */
@Slf4j
@AllArgsConstructor
public class SecureInvokeService {

  public static final int RETRY_INTERVAL_SECOND = 10;

  private final SecureInvokeRecordService secureInvokeRecordService;

  private final Executor executor;

  /** 5s重试 */
  @Scheduled(cron = "*/5 * * * * ?")
  public void run() {
    List<SecureInvokeRecord> waitRetryRecords = secureInvokeRecordService.getWaitRetryRecords();
    waitRetryRecords.forEach(this::doInvoke);
  }

  public void save(SecureInvokeRecord record) {
    secureInvokeRecordService.save(record);
  }

  private void retryRecord(SecureInvokeRecord record, String errorMsg) {
    int retryTimes = record.getRetryTimes() + 1;
    SecureInvokeRecord update = new SecureInvokeRecord();
    update.setId(record.getId());
    update.setFailReason(errorMsg);
    update.setNextRetryTime(LocalDateTime.now().plusSeconds(retryTimes));
    if (retryTimes > record.getMaxRetryTimes()) {
      update.setStatus(SecureInvokeRecord.STATUS_FAIL);
    } else {
      update.setRetryTimes(retryTimes);
    }
    secureInvokeRecordService.updateById(update);
  }

  private void removeRecord(Long id) {
    secureInvokeRecordService.removeById(id);
  }

  public void invoke(SecureInvokeRecord record, boolean async) {
    // 非事务状态，直接执行
    if (!TransactionSynchronizationManager.isActualTransactionActive()) {
      return;
    }
    // 保存执行数据
    save(record);
    TransactionSynchronizationManager.registerSynchronization(
        new TransactionSynchronization() {
          @SneakyThrows
          @Override
          public void afterCommit() {
            // 事务后执行
            if (async) {
              doAsyncInvoke(record);
            } else {
              doInvoke(record);
            }
          }
        });
  }

  public void doAsyncInvoke(SecureInvokeRecord record) {
    executor.execute(() -> doInvoke(record));
  }

  public void doInvoke(SecureInvokeRecord record) {
    if (Objects.isNull(record)) {
      return;
    }
    SecureInvokeDTO secureInvokeDTO = record.getSecureInvokeDTO();
    if (Objects.isNull(secureInvokeDTO)) {
      return;
    }
    try {
      SecureInvokeHolder.setInvoking();
      Class<?> beanClass = Class.forName(secureInvokeDTO.getClassName());
      Object bean = SpringUtil.getBean(beanClass);
      List<String> parameterStrings =
          JSON.parseArray(secureInvokeDTO.getParameterTypes(), String.class);
      List<Class<?>> parameterClasses = getParameters(parameterStrings);
      Method method =
          ReflectUtil.getMethod(
              beanClass, secureInvokeDTO.getMethodName(), parameterClasses.toArray(new Class[] {}));
      Object[] args = getArgs(secureInvokeDTO, parameterClasses);
      // 执行方法
      method.invoke(bean, args);
      // 执行成功更新状态
      removeRecord(record.getId());
    } catch (Throwable e) {
      log.error("SecureInvokeService invoke fail", e);
      // 执行失败，等待下次执行
      retryRecord(record, e.getMessage());
    } finally {
      SecureInvokeHolder.invoked();
    }
  }

  @NotNull
  private Object[] getArgs(SecureInvokeDTO secureInvokeDTO, List<Class<?>> parameterClasses) {
    JSONArray jsonArray = JSON.parseArray(secureInvokeDTO.getArgs());
    Object[] args = new Object[jsonArray.size()];
    for (int i = 0; i < jsonArray.size(); i++) {
      Class<?> aClass = parameterClasses.get(i);
      args[i] = jsonArray.getObject(i, aClass);
    }
    return args;
  }

  @NotNull
  private List<Class<?>> getParameters(List<String> parameterStrings) {
    return parameterStrings.stream()
        .map(
            name -> {
              try {
                return Class.forName(name);
              } catch (ClassNotFoundException e) {
                log.error("SecureInvokeService class not fund", e);
              }
              return null;
            })
        .collect(Collectors.toList());
  }
}
